﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using Protobuf_Gui.Misc;

namespace Protobuf_Gui.Domain
{
    [Serializable]
    public abstract class ProtocolElement
    {
        public ProtocolElement(string name)
        {
            Name = name;
        }

        public static T Create<T>(string name)where T:ProtocolElement
        {
            Type type = typeof (T);
            ConstructorInfo constructor = type.GetConstructor(new[] {typeof (string)});
            if (constructor != null)
            {
                object o = constructor.Invoke(new object[] {name});
                return (T)o;
            }
            return null;
        }

        public abstract TreeNode ToTreeNode(TreeNode root);

        public abstract void ToFile(TextWriter writer, uint nesting);

        public string Name { get; set; }

        public override string ToString()
        {
            return Name;
        }

        public static TreeNode CreateNode(ProtocolElement element)
        {
            return new TreeNode(element.Name) {Tag = element};
        }

        //public abstract bool CanAdd(string name);

        protected void CheckEmpty(string name, string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                throw new ProtocolException("value must not be empty"){FieldName = name};
            }
        }

        protected void CheckDuplicated(IEnumerable<ProtocolElement> elements)
        {
            var forCheckList = new List<ProtocolElement>();

            var exception = new ProtocolException("Duplicated entities");

            foreach (var element in elements)
            {
                if (forCheckList.Contains(element))
                {
                    exception.AddDuplicated(element);
                }
                else
                {
                    forCheckList.Add(element);
                }
            }
            forCheckList.Clear();

            if (exception.DuplicatedElements.Count > 0)
            {
                throw exception;
            }
        }

        private bool Equals(ProtocolElement other)
        {
            return string.Equals(Name, other.Name);
        }

        public void Add(Type listType, object value)
        {
            PropertyInfo[] properties = this.GetType().GetProperties();
            foreach (var propertyInfo in properties)
            {
                Type type = propertyInfo.PropertyType;
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (List<>))
                {
                    if (type.GetGenericArguments()[0] == listType)
                    {
                        MethodInfo method = propertyInfo.GetGetMethod();
                        var list = method.Invoke(this, null) as IList;
                        if (list != null) 
                            list.Add(value);
                    }
                }
            }
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((ProtocolElement) obj);
        }

        public IEnumerable<ItemTypeDesc> InnerTypes()
        {
            return this.GetType().GetCustomAttributes(typeof (ContainTypeAttribute),
                true).Cast<ContainTypeAttribute>().Select(attribute => new ItemTypeDesc(attribute.Type, attribute.Name));
        }

        public override int GetHashCode()
        {
            return (Name != null ? Name.GetHashCode() : 0);
        }

        protected static string Tabs(uint count)
        {
            return Enumerable.Range(0, (int) count).Aggregate("", (x, s) => x + "\t");
        }

        public static T Load<T>(string filePath) where T : class
        {
            return ConfigLoader.LoadBinary<T>(filePath);
        }

        public bool Save(string filePath)
        {
            return ConfigLoader.SaveBinary(this, filePath);
        }
    }

    class Folder : ProtocolElement
    {
        public Folder(string name) : base(name)
        {
        }

        public override TreeNode ToTreeNode(TreeNode root)
        {
            throw new NotImplementedException();
        }

        public override void ToFile(TextWriter writer, uint nesting)
        {

        }
    }
}